﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using DynORM.Interfaces;
using System.Xml.Serialization;
using System.Xml;
using System.Xml.Schema;
using DynORM.TypeMappingSubsystem;

namespace DynORM.ValueObjects
{

    [XmlRoot(ElementName = PrimaryKey.PrimaryKeyTag)]
    public class PrimaryKey : IPrimaryKey, IXmlSerializable
    {
        private RelationType relationType; //had to relax "readonly" restriction of this member in favor to init logic of xml serializer
        private string tableName; //had to relax "readonly" restriction of this member in favor to init logic of xml serializer
        private Dictionary<int, IField> fields; //had to relax "readonly" restriction of this member in favor to init logic of xml serializer
        private string name; //had to relax "readonly" restriction of this member in favor to init logic of xml serializer

        //for serializer
        private PrimaryKey()
        {
            this.relationType = RelationType.PrimaryKey;
            this.fields = new Dictionary<int, IField>();
        }

        public const string PrimaryKeyTag = "PrimaryKey";
        public const string PrimaryKeyNameAttr = "Name";
        public const string PrimaryKeyTableNameAttr = "TableName";
        public const string PrimaryKeyFieldsTag = "PrimaryKeyFields";

        public PrimaryKey(string tableName, IDictionary<int, IField> fields, string name)
        {
            this.relationType = RelationType.PrimaryKey;
            this.fields = new Dictionary<int, IField>();

            foreach (KeyValuePair<int, IField> f in fields)
                this.fields.Add(f.Key, f.Value);

            this.tableName = tableName ?? String.Empty;

            this.name = name;
        }

        public PrimaryKey(IPrimaryKeyDescriptor relationDescriptor, params object[] values)
        {
            this.relationType = RelationType.PrimaryKey;
            this.fields = new Dictionary<int, IField>();

            int i = 0;
            foreach (KeyValuePair<int, IFieldDescriptor> kv in relationDescriptor.FieldDescriptors.OrderBy<KeyValuePair<int, IFieldDescriptor>,int>(fd=>fd.Key))
                this.fields.Add(kv.Key, new Field(kv.Value, values[i++]));
            this.tableName = relationDescriptor.TableName;
            this.name = relationDescriptor.Name;
        }

        public RelationType RelationType
        {
            get { return this.relationType; }
        }

        public string TableName
        {
            get
            {
                return this.tableName;
            }
        }

        public IDictionary<int, IField> Fields
        {
            get
            {
                return this.fields;
            }
        }

        public CheckIntegrityResult CheckValuesIntegrity(bool usePredefinedConvention)
        {
            CheckIntegrityResult passed = new CheckIntegrityResult(true, String.Empty);

            //just because we construct Relations not only from RelationDescriptors we have to recheck structure integrity before value integrity
            CheckIntegrityResult checkStructureIntegrityRes = this.GetDescriptor().CheckStructureIntegrity(false);
            if (checkStructureIntegrityRes != passed)
                return checkStructureIntegrityRes;

            foreach (KeyValuePair<int, IField> f in this.fields)
            {
                if (f.Value.CheckValuesIntegrity(false) != passed)
                    return new CheckIntegrityResult(false, "One field composing the primary key failed its structure integrity check. See inner CheckIntegrityResult.", f.Value.CheckValuesIntegrity(false));

                if (usePredefinedConvention)
                {
                    if (!((f.Value.FieldType == PrimaryKeyDescriptor.PredefinedTypeForIDs) || (f.Value.FieldType == Types.GetCLRNullableType(PrimaryKeyDescriptor.PredefinedTypeForIDs))))
                        return new CheckIntegrityResult(false, String.Format("Field type must be {0}.", PrimaryKeyDescriptor.PredefinedTypeForIDs.Name));
                    if (!Regex.IsMatch(f.Value.FieldName, String.Format(@"{0}.*", PrimaryKeyDescriptor.PredefinedFieldNameForIDs)))
                        return new CheckIntegrityResult(false, String.Format("Field name must be {0} (or {0}_n in case of composite key).", PrimaryKeyDescriptor.PredefinedFieldNameForIDs));
                }
            }

            return passed;
        }

        public CheckIntegrityResult CheckOnlyValuesIntegrity(bool usePredefinedConvention)
        {
            //this method is used in case when several rows from the same table are retrieved. in this case structure (RowDescriptor) is checked only once and values (Rows) check do not include structure tests
            CheckIntegrityResult passed = new CheckIntegrityResult(true, String.Empty);

            foreach (KeyValuePair<int, IField> f in this.fields)
            {
                if (f.Value.CheckOnlyValuesIntegrity(false) != passed)
                    return new CheckIntegrityResult(false, "One field composing the primary key failed its structure integrity check. See inner CheckIntegrityResult.", f.Value.CheckValuesIntegrity(false));

                if (usePredefinedConvention)
                {
                    if (!((f.Value.FieldType == PrimaryKeyDescriptor.PredefinedTypeForIDs) || (f.Value.FieldType == Types.GetCLRNullableType(PrimaryKeyDescriptor.PredefinedTypeForIDs))))
                        return new CheckIntegrityResult(false, String.Format("Field type must be {0}.", PrimaryKeyDescriptor.PredefinedTypeForIDs.Name));
                    if (!Regex.IsMatch(f.Value.FieldName, String.Format(@"{0}.*", PrimaryKeyDescriptor.PredefinedFieldNameForIDs)))
                        return new CheckIntegrityResult(false, String.Format("Field name must be {0} (or {0}_n in case of composite key).", PrimaryKeyDescriptor.PredefinedFieldNameForIDs));
                }
            }

            return passed;
        }

        public string Name
        {
            get
            {
                return this.name;
            }
        }

        public IORMObjectDescriptor GetDescriptor()
        {
            Dictionary<int, IFieldDescriptor> fieldDescriptors = new Dictionary<int, IFieldDescriptor>();
            foreach (KeyValuePair<int, IField> f in this.fields)
                fieldDescriptors.Add(f.Key, (IFieldDescriptor)f.Value.GetDescriptor());
            return new PrimaryKeyDescriptor(this.tableName, fieldDescriptors, this.Name);
        }

        #region Value-based equality semantics implementation
        public override bool Equals(object obj)
        {
            //PrimaryKey that = obj as PrimaryKey;
            //if (Object.ReferenceEquals(that, null))
            if (!(obj is PrimaryKey))
                return false;
            PrimaryKey that = (PrimaryKey)obj;

            //need an explicit cast cause i wanna use overloaded operator for PrimaryKey. GetDescriptor return IORMObjectDescriptor
            if ((PrimaryKeyDescriptor)this.GetDescriptor() != (PrimaryKeyDescriptor)that.GetDescriptor())
                return false;

            for (int i = 0; i < this.Fields.Count; i++ ) //we can be sure in equality of field number because descriptors are equal
            {
                //and again, need an explicit cast cause i wanna use overloaded operator for Field. For IField do not used the overloaded operator
                Field thisF = (Field)this.Fields[i];
                Field thatF = (Field)that.Fields[i];
                if (thisF != thatF) return false;
            }

            return true;
        }

        public override int GetHashCode()
        {
            return this.TableName.GetHashCode();
        }

        public static bool operator ==(PrimaryKey r1, IRelation r2)
        {
            if (Object.ReferenceEquals(r1, null))
                return false;

            return r1.Equals(r2);
        }

        public static bool operator !=(PrimaryKey r1, IRelation r2)
        {
            if (Object.ReferenceEquals(r1, null))
                return false;

            return !r1.Equals(r2);
        }

        #endregion

        #region IXmlSerializable and auxiliary CreateFromXml member
        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            int i = 0;

            do
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        {
                            if (string.Compare(reader.Name, PrimaryKeyTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                bool emptyElement = reader.IsEmptyElement;
                                if (reader.HasAttributes)
                                {
                                    while (reader.MoveToNextAttribute())
                                    {
                                        //if (string.Compare(reader.Name, PrimaryKeyUniqueNameAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                        //    this.uniqueName = reader.Value;
                                        if (string.Compare(reader.Name, PrimaryKeyNameAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                            this.name = reader.Value;
                                        else if (string.Compare(reader.Name, PrimaryKeyTableNameAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                            this.tableName = reader.Value;
                                    }
                                }
                                if (emptyElement)
                                    return;
                            }
                            else if (string.Compare(reader.Name, PrimaryKeyFieldsTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {  }
                            else if (string.Compare(reader.Name, Field.FieldTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                Field f = Field.CreateFromXml(reader);
                                this.fields.Add(i, f);
                                i++;
                            }
                        } break;
                    case XmlNodeType.EndElement:
                        {
                            if (string.Compare(reader.Name, PrimaryKeyTag, StringComparison.OrdinalIgnoreCase) == 0)
                                return;
                        } break;
                    default: continue;
                }
            } while (reader.Read());
        }

        public static PrimaryKey CreateFromXml(XmlReader reader)
        {
            //string uniqueName = String.Empty;
            string name = String.Empty;
            string tableName = String.Empty;
            Dictionary<int, IField> fields = new Dictionary<int, IField>();
            int i = 0;

            do
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        {
                            if (string.Compare(reader.Name, PrimaryKeyTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                bool emptyElement = reader.IsEmptyElement;
                                if (reader.HasAttributes)
                                {
                                    while (reader.MoveToNextAttribute())
                                    {
                                        //if (string.Compare(reader.Name, PrimaryKeyUniqueNameAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                        //    uniqueName = reader.Value;
                                        if (string.Compare(reader.Name, PrimaryKeyNameAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                            name = reader.Value;
                                        else if (string.Compare(reader.Name, PrimaryKeyTableNameAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                            tableName = reader.Value;
                                    }
                                }
                                if (emptyElement)
                                    return new PrimaryKey(tableName, fields, name);
                            }
                            else if (string.Compare(reader.Name, PrimaryKeyFieldsTag, StringComparison.OrdinalIgnoreCase) == 0)
                            { }
                            else if (string.Compare(reader.Name, Field.FieldTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                Field f = Field.CreateFromXml(reader);
                                fields.Add(i, f);
                                i++;
                            }
                        } break;
                    case XmlNodeType.EndElement:
                        {
                            if (string.Compare(reader.Name, PrimaryKeyTag, StringComparison.OrdinalIgnoreCase) == 0)
                                return new PrimaryKey(tableName, fields, name);
                        } break;
                    default: continue;
                }
            } while (reader.Read());
            return new PrimaryKey(tableName, fields, name);
        }

        public void WriteXml(XmlWriter writer)
        {
            //writer.WriteAttributeString(PrimaryKeyUniqueNameAttr, this.UniqueName);
            writer.WriteAttributeString(PrimaryKeyNameAttr, this.Name);
            writer.WriteAttributeString(PrimaryKeyTableNameAttr, this.TableName);
            if (this.Fields.Count() > 0)
            {
                writer.WriteStartElement(PrimaryKeyFieldsTag);
                foreach (KeyValuePair<int, IField> f in this.Fields.OrderBy<KeyValuePair<int, IField>, int>(kv => kv.Key))
                {
                    writer.WriteStartElement(Field.FieldTag);
                    ((Field)f.Value).WriteXml(writer);
                    writer.WriteEndElement();
                }
                writer.WriteEndElement();
            }
        }
        #endregion    

    }

}